package org.dfl.core.model;

import org.dfl.messages.Messages;

import java.util.HashSet;
import java.util.Set;

import org.dfl.core.data.types.TypeContainer;
import org.dfl.core.data.types.TypeManager;
import org.dfl.core.model.listeners.EdgeListener;
import org.dfl.core.model.transitions.Transition;
import org.jdom.Element;



/**
 * class that represents the edge between a place and a transition
 * @author Piotr Wlodarczyk
 *
 */
public abstract class Edge implements IOElement{
	private Place place;
	private Transition transition;
	private String id = null;
	private boolean nested = false;
	private boolean neededTrue = false;
	private boolean neededFalse = false;
	private boolean neededEmpty = false;
	private boolean neededNotempty = false;
	private TypeContainer typeContainer = new TypeContainer();
	private boolean connected = false;
	
	//nasluchiwacze zmian
	private Set<EdgeListener> listeners = new HashSet<EdgeListener>();
	
	public String getId() {
		if(id == null){
			regenerateId();
		}
		return id;
	}

	public void setId(String id) {
		if(!IdGenerator.isRegistered(id)){
			IdGenerator.registerId(id);
			this.id = id;
		}else{
			regenerateId();
		}
	}
	
	protected void regenerateId(){
		this.id = IdGenerator.getGeneratedId(this.getClass());
	}
	
	public TypeContainer getTypeContainer() {
		return typeContainer;
	}

	/** extraction **/
	public void setNested(boolean nested) {
		this.nested = nested;
	}

	public boolean isNested() {
		return nested;
	}
	
	/** properties **/
	public boolean isNeededEmpty() {
		return neededEmpty;
	}
	public boolean isNeededFalse() {
		return neededFalse;
	}
	public boolean isNeededNotempty() {
		return neededNotempty;
	}
	public boolean isNeededTrue() {
		return neededTrue;
	}
	/********************* zmienienie typu **********
	 
	 * @throws Exception */
	public void resetProperties(){
		boolean reconnect = false;
		if(isConnected()) {
			disconnect();
			reconnect = true;
		};
		clearProperties();
		this.typeContainer = new TypeContainer();
		
		try {
			if(reconnect) reconnect();
		} catch (Exception e) {
			// nie ma prawa sie wydarzyc! 
		}
	}
	
	protected void clearProperties(){
		this.neededEmpty = false;
		this.neededNotempty = false;
		this.neededFalse = false;
		this.neededTrue = false;
	}

	public void setNeededEmpty() throws Exception {
		boolean reconnect = false;
		if(isConnected()) {
			disconnect();
			reconnect = true;
		}
		clearProperties();
		this.neededEmpty = true;
		this.typeContainer = new TypeContainer(TypeManager.getCollectionType());
		if(reconnect) reconnect();
	}
	public void setNeededNotempty() throws Exception {
		boolean reconnect = false;
		if(isConnected()) {
			disconnect();
			reconnect = true;
		}
		clearProperties();
		this.neededNotempty = true;
		this.typeContainer = new TypeContainer(TypeManager.getCollectionType());
		if(reconnect) reconnect();
	}

	public void setNeededFalse() throws Exception {
		boolean reconnect = false;
		if(isConnected()) {
			disconnect();
			reconnect = true;
		}
		if(isConnected()) {
			disconnect();
			reconnect = true;
		}
		clearProperties();
		this.neededFalse = true;
		this.typeContainer = new TypeContainer(TypeManager.getBooleanType());
		if(reconnect) reconnect();
	}
	public void setNeededTrue() throws Exception {
		boolean reconnect = false;
		if(isConnected()) {
			disconnect();
			reconnect = true;
		}
		clearProperties();
		this.neededTrue = true;
		this.typeContainer = new TypeContainer(TypeManager.getBooleanType());
		if(reconnect) reconnect();
	}

	/************** koniec ****/
	public Place getPlace() {
		return place;
	}

	public Transition getTransition() {
		return transition;
	}
	
	public abstract void connectAction(Place place, Transition transition) throws Exception;
	
	public final void connect(Place place, Transition transition) throws Exception{
		if(!isConnected()){
			if(place == null || transition == null){
				throw new Exception(Messages.Edge_errorEmptyTransitionOrPlace);
			}
			this.place = place;
			this.transition = transition;
			connectAction(place, transition);
			connected();
		}
	}
	
	public abstract void disconnectAction();
	
	public final void disconnect(){
		if(isConnected()){
			getTypeContainer().separate();
			disconnectAction();
			disconnected();
		}
	}
	
	public void reconnect() throws Exception{
		connect(getPlace(),getTransition());
	}
	
	/**
	 * wczytywanie lacznika - polaczenie musi byc 
	 * @param pElem - element xmlowy
	 * @throws XMLLoaderException
	 */
	public void load(Element eElem)  throws XMLLoaderException {
		this.id = eElem.getAttributeValue("id"); // id ustawiany "recznie" - bez sprawdzania idkow //$NON-NLS-1$
		IdGenerator.registerId(this.id);

		try{
			Element tsElem = eElem.getChild("toolspecific"); //$NON-NLS-1$
			if(tsElem != null){
				setNested(tsElem.getChild("expand") != null); //$NON-NLS-1$
				
				if(tsElem.getChild("empty") != null) setNeededEmpty(); //$NON-NLS-1$
				if(tsElem.getChild("notempty") != null) setNeededNotempty(); //$NON-NLS-1$
				if(tsElem.getChild("true") != null) setNeededTrue(); //$NON-NLS-1$
				if(tsElem.getChild("false") != null) setNeededFalse(); //$NON-NLS-1$
			}
		}catch(Exception e){
			throw new XMLLoaderException(e);
		}
	}
	
	public Element save() {
		Element tElem = new Element("arc"); //$NON-NLS-1$
		tElem.setAttribute("id",getId()); //$NON-NLS-1$
		
		Element toolspecificElem = new Element("toolspecific"); //$NON-NLS-1$
		toolspecificElem.setAttribute("tool",""); //$NON-NLS-1$ //$NON-NLS-2$
		toolspecificElem.setAttribute("version",""); //$NON-NLS-1$ //$NON-NLS-2$
		tElem.addContent(toolspecificElem);

		if(isNested())	toolspecificElem.addContent(new Element("expand")); //$NON-NLS-1$
		
		if(isNeededEmpty())	toolspecificElem.addContent(new Element("empty")); //$NON-NLS-1$
		if(isNeededNotempty())	toolspecificElem.addContent(new Element("notempty")); //$NON-NLS-1$
		if(isNeededFalse())	toolspecificElem.addContent(new Element("false")); //$NON-NLS-1$
		if(isNeededTrue())	toolspecificElem.addContent(new Element("true")); //$NON-NLS-1$

		return tElem;
	}
	
	
	//listeners
	public void addListener(EdgeListener l){
		listeners.add(l);
	}
	public void removeListener(EdgeListener l){
		listeners.remove(l);
	}
	
	
	protected void connected(){
		setConnected(true);
		for(EdgeListener l : listeners){
			l.edgeConnected(this);
		}
	}
	protected void disconnected(){
		setConnected(false);
		for(EdgeListener l : listeners){
			l.edgeDisconnected(this);
		}
	}

	public boolean isConnected() {
		return connected;
	}

	public void setConnected(boolean connected) {
		this.connected = connected;
	}
}
